package mycsveditor.editors;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.contentassist.CompletionProposal;
import org.eclipse.jface.text.contentassist.ContextInformation;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContentAssistProcessor;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.jface.text.contentassist.IContextInformationValidator;
import org.eclipse.jface.text.rules.ITokenScanner;

public class CSVContentAssistProcessor implements IContentAssistProcessor {

	// TODO change me according to real application
	private static final String DEFAULT_PACKAGE = "info";

	private JavaAnalysisUtil fJavaAnalysisUtil;

	public CSVContentAssistProcessor(JavaAnalysisUtil javaAnalysisUtil) {
		super();
		this.fJavaAnalysisUtil = javaAnalysisUtil;
	}

	public CSVContentAssistProcessor(ITokenScanner scanner) {
		super();
	}

	@Override
	public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer,
			int offset) {
		LineInfo lineInfo = new LineInfo(viewer.getDocument(), offset);
		ProposalComputer pc = ProposalComputer.create(lineInfo,
				fJavaAnalysisUtil);
		if (pc == null) {
			return null;
		}
		return pc.compute();
	}

	public static final class ProposalComparator implements
			Comparator<ICompletionProposal> {

		private String text;

		public ProposalComparator(String text) {
			this.text = text;
		}

		@Override
		public int compare(ICompletionProposal o1, ICompletionProposal o2) {
			String display1 = o1.getDisplayString().toLowerCase();
			String display2 = o2.getDisplayString().toLowerCase();
			if (display1.startsWith(text) && !display2.startsWith(text)) {
				return -1;
			}
			if (display2.startsWith(text) && !display1.startsWith(text)) {
				return 1;
			}
			return display1.compareTo(display2);
		}

	}

	public static abstract class ProposalComputer {

		private static final Pattern CLASS_PATTERN = Pattern
				.compile("(?i)(^[^,]*?)\\.([^,]*)$");
		private static final Pattern METHOD_PATTERN = Pattern
				.compile("(?i)^([^,]+),([^,]*)$");
		private static final Pattern PARAMETER_PATTERN = Pattern
				.compile("(?i)^([^,]+,[^,]+),$");

		protected LineInfo lineInfo;

		protected JavaAnalysisUtil javaAnalysisUtil;

		public ProposalComputer(LineInfo lineInfo,
				JavaAnalysisUtil javaAnalysisUtil) {
			this.lineInfo = lineInfo;
			this.javaAnalysisUtil = javaAnalysisUtil;
		}

		public abstract ICompletionProposal[] compute();

		public static ProposalComputer create(LineInfo lineInfo,
				JavaAnalysisUtil javaAnalysisUtil) {
			String part = lineInfo.getLinePart();
			Matcher m = CLASS_PATTERN.matcher(part);
			if (m.matches()) {
				return new ClassProposalComputer(lineInfo, javaAnalysisUtil);
			}
			m = METHOD_PATTERN.matcher(part);
			if (m.matches()) {
				return new MethodProposalComputer(lineInfo, javaAnalysisUtil);
			}
			m = PARAMETER_PATTERN.matcher(part);
			if (m.matches()) {
				return new ParameterProposalComputer(lineInfo, javaAnalysisUtil);
			}
			return null;
		}

		protected void processProposals(List<ICompletionProposal> result,
				String prefix) {
			if (result.size() < 1) {
				ICompletionProposal proposal = new CompletionProposal("",
						lineInfo.getOffset(), 0, 0, null,
						"no suggestion available", null, null);
				result.add(proposal);
			}
			Collections.sort(result, new ProposalComparator(prefix));
		}

		protected boolean isEmptyParameter(IMethod method) {
			return method.getParameterTypes().length == 0;
		}

		protected boolean isSinglMapParameter(IMethod method) {
			String[] types = method.getParameterTypes();
			if (types.length != 1) {
				return false;
			}
			if (types[0].endsWith("Map;")) {
				return true;
			} else {
				return false;
			}
		}

		protected boolean isCallable(IMethod method) {
			return isEmptyParameter(method) || isSinglMapParameter(method);
		}

		protected ICompletionProposal[] resolveProposals(
				List<ICompletionProposal> result, String prefix) {
			processProposals(result, prefix);
			return result.toArray(new ICompletionProposal[result.size()]);
		}
	}

	public static class ClassProposalComputer extends ProposalComputer {

		public ClassProposalComputer(LineInfo lineInfo,
				JavaAnalysisUtil javaAnalysisUtil) {
			super(lineInfo, javaAnalysisUtil);
		}

		@Override
		public ICompletionProposal[] compute() {
			List<ICompletionProposal> result = new ArrayList<ICompletionProposal>();
			JavaCallNames jcn = new JavaCallNames(lineInfo.getLinePart());
			String pkgStr = jcn.getPkg();
			if ("".equals(pkgStr)) {
				pkgStr = DEFAULT_PACKAGE;
			}
			if (!pkgStr.startsWith(DEFAULT_PACKAGE + ".")
					&& !(pkgStr.equals(DEFAULT_PACKAGE))) {
				pkgStr = DEFAULT_PACKAGE + "." + pkgStr;
			}
			IPackageFragment pkg = javaAnalysisUtil.getPackage(pkgStr);
			if (pkg == null) {
				pkgStr = DEFAULT_PACKAGE;
				pkg = javaAnalysisUtil.getPackage(pkgStr);
			}
			if (pkg == null) {
				return resolveProposals(result, jcn.getClazz());
			}
			List<IType> clazzes = javaAnalysisUtil.searchTypes2(pkg
					.getElementName(), jcn.getClazz());
			for (IType clazz : clazzes) {
				String packageName = getPackageName(clazz);
				String clazzName = clazz.getElementName();
				String replace = packageName + "." + clazzName;
				String display = clazzName + " - " + packageName;
				ICompletionProposal proposal = new CompletionProposal(replace,
						lineInfo.getRegion().getOffset(), lineInfo
								.getOffsetInLine(), replace.length(), null,
						display, null, null);
				result.add(proposal);
			}
			return resolveProposals(result, jcn.getClazz());
		}

		private String getPackageName(IType clazz) {
			IJavaElement parent = clazz.getParent();
			while (!(parent instanceof IPackageFragment)
					&& !(parent instanceof IJavaProject)) {
				parent = parent.getParent();
			}
			if (parent instanceof IJavaProject) {
				return "";
			}
			return parent.getElementName();
		}
	}

	public static class MethodProposalComputer extends ProposalComputer {

		public MethodProposalComputer(LineInfo lineInfo,
				JavaAnalysisUtil javaAnalysisUtil) {
			super(lineInfo, javaAnalysisUtil);
		}

		@Override
		public ICompletionProposal[] compute() {
			List<ICompletionProposal> result = new ArrayList<ICompletionProposal>();
			JavaCallNames jcn = new JavaCallNames(lineInfo.getLinePart());
			IType clazz = javaAnalysisUtil.getClazz(jcn);
			if (clazz == null) {
				return resolveProposals(result, jcn.getMethod());
			}
			List<IMethod> methods = javaAnalysisUtil.searchMethods(clazz, jcn
					.getMethod());
			for (IMethod method : methods) {
				if (!isCallable(method)) {
					continue;
				}
				String addtion = null;
				MethodDeclaration md = javaAnalysisUtil
						.findMethodDeclaration(method);
				if (md != null && md.getJavadoc() != null) {
					addtion = md.getJavadoc().toString();
				} else {
					addtion = "java doc not specified";
				}
				String name = method.getElementName();
				ICompletionProposal proposal = new CompletionProposal(name,
						lineInfo.getOffset() - jcn.getMethod().length(), jcn
								.getMethod().length(), name.length(), null,
						name, null, addtion);
				result.add(proposal);
			}
			return resolveProposals(result, jcn.getMethod());
		}

	}

	public static class ParameterProposalComputer extends ProposalComputer {

		public ParameterProposalComputer(LineInfo lineInfo,
				JavaAnalysisUtil javaAnalysisUtil) {
			super(lineInfo, javaAnalysisUtil);
		}

		@Override
		public ICompletionProposal[] compute() {
			List<ICompletionProposal> result = new ArrayList<ICompletionProposal>();
			String part = lineInfo.getLinePart().substring(0,
					lineInfo.getLinePart().length() - 1);
			IMethod method = javaAnalysisUtil
					.getMethod(new JavaCallNames(part));
			if (method == null) {
				return resolveProposals(result, "");
			}
			if (!isSinglMapParameter(method)) {
				return resolveProposals(result, "");
			}
			FullnameMatcher matcher = new FullnameMatcher();
			List<MethodInvocation> invokes = javaAnalysisUtil
					.searchMethodParameterInvocation(method, matcher);
			Set<ParameterDescriptor> params = matcher
					.resolveParameters(invokes);
			String replace = "", display = "";
			for (ParameterDescriptor param : params) {
				display += param.toString() + ",";
				replace += param.getName() + "=,";
			}
			if (!replace.equals("")) {
				display = display.substring(0, display.length() - 1);
				replace = replace.substring(0, replace.length() - 1);
				ICompletionProposal proposal = new CompletionProposal(replace,
						lineInfo.getOffset(), 0, replace.length(), null,
						display, null, null);
				result.add(proposal);
			}
			return resolveProposals(result, "");
		}

	}

	@Override
	public IContextInformation[] computeContextInformation(ITextViewer viewer,
			int offset) {
		return new IContextInformation[] { new ContextInformation(null,
				"context display string", "information display string") };
	}

	@Override
	public char[] getCompletionProposalAutoActivationCharacters() {
		return new char[] { '.', ',' };
	}

	@Override
	public char[] getContextInformationAutoActivationCharacters() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public IContextInformationValidator getContextInformationValidator() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public String getErrorMessage() {
		// TODO Auto-generated method stub
		return null;
	}

}
